package be.bendem.manga.library.controllers; import be.bendem.manga.library.MangaLibrary; import be.bendem.manga.library.utils.NumberUtil; import be.bendem.manga.library.utils.Tuple; import javafx.beans.property.IntegerPropertyBase; import javafx.beans.property.SimpleIntegerProperty; import javafx.collections.FXCollections; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.fxml.Initializable; import javafx.scene.Node; import javafx.scene.Parent; import javafx.scene.control.Accordion; import javafx.scene.control.ListView; import javafx.scene.control.TitledPane; import javafx.scene.control.cell.TextFieldListCell; import javafx.scene.layout.AnchorPane; import javafx.scene.text.Font; import java.io.IOException; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.ResourceBundle; import java.util.stream.Collectors; import java.util.stream.Stream; public class MangaLibraryCtrl implements Initializable { @FXML private Accordion accordion; @FXML private AnchorPane main; private final MangaLibrary app; private final Map<String, Tuple<Parent, Object>> mainCache; private String mainCurrentFxml; private Object mainCurrentCtrl; public MangaLibraryCtrl(MangaLibrary app) { this.app = app; mainCache = new HashMap<>(); mainCurrentFxml = ""; } @Override @SuppressWarnings("") public void initialize(URL location, ResourceBundle resources) { setMain("search.fxml"); String libraryLocation = app.getConfigManager().getApplicationConfig().get("libraryLocation"); Path library = Paths.get(libraryLocation); collectDirectories(library) .forEachOrdered(manga -> addManga( manga.getFileName().toString(), collectDirectories(manga) .map(subPath -> subPath.getFileName().toString()) .sorted(NumberUtil::compare) .collect(Collectors.toList()) ) ); } Stream<Path> collectDirectories(Path path) { if(!Files.isDirectory(path)) { return Stream.empty(); } try { return Files.walk(path, 1) .filter(subPath -> !subPath.equals(path)) .filter(Files::isDirectory); } catch(IOException e) { throw new RuntimeException("Failed to collect directories from " + path, e); } } @SuppressWarnings("unchecked") public <T> T setMain(String fxml) { if(mainCurrentFxml.equals(fxml)) { return (T) mainCurrentCtrl; } Tuple<Parent, Object> parentCtrlTuple = mainCache.get(fxml); if(parentCtrlTuple == null) { FXMLLoader loader = new FXMLLoader(getClass().getClassLoader().getResource(fxml)); loader.setControllerFactory(app.controllerFactory); try { parentCtrlTuple = new Tuple<>(loader.load(), loader.getController()); mainCache.put(fxml, parentCtrlTuple); } catch(IOException e) { throw new RuntimeException(e); } } setMain(parentCtrlTuple.getLeft()); app.pushHistory(mainCurrentFxml); mainCurrentFxml = fxml; mainCurrentCtrl = parentCtrlTuple.getRight(); if(mainCurrentCtrl instanceof PostInitializable) { ((PostInitializable) mainCurrentCtrl).afterInitialization(); } return (T) mainCurrentCtrl; } private void setMain(Parent parent) { main.getChildren().setAll(parent); } private void addManga(String name, List<String> chapters) { ListView<String> content = new ListView<>(FXCollections.observableArrayList(chapters)); content.getSelectionModel().selectedItemProperty().addListener( (obs, oldVal, newVal) -> this .<MangaViewCtrl>setMain("manga-view.fxml") .setManga(name) .setChapter(newVal, false) ); // TODO Load that value, default to -1 SimpleIntegerProperty readMarkerIndex = new SimpleIntegerProperty(-1); content.getSelectionModel().selectedIndexProperty().addListener( (obs, oldVal, newVal) -> { if(newVal.intValue() > readMarkerIndex.get()) { // TODO Save the new value readMarkerIndex.setValue(newVal); } } ); content.setCellFactory(listView -> new MarkableTextFieldListCell(readMarkerIndex, "read-marker")); TitledPane pane = new TitledPane(name, content); pane.setFont(new Font(15)); accordion.getPanes().add(pane); } public void setSelection(String manga, String chapter) { Node content = accordion.getPanes().stream() .filter(pane -> pane.getText().equals(manga)) .findFirst() .get() .getContent(); ListView<String> listView = (ListView<String>) content; listView.getSelectionModel().select(chapter); listView.scrollTo(chapter); } public void onSearchAction(ActionEvent event) { setMain("search.fxml"); } public void onDownloadsAction(ActionEvent event) { setMain("downloads.fxml"); } public void onConfigAction(ActionEvent event) { setMain("config.fxml"); } public void onImportAction(ActionEvent event) { // TODO Open file chooser } private class MarkableTextFieldListCell<T> extends TextFieldListCell<T> { private final IntegerPropertyBase index; private final String styleClass; private boolean marked = false; public MarkableTextFieldListCell(IntegerPropertyBase index, String styleClass) { this.index = index; this.styleClass = styleClass; index.addListener((obs, oldVal, newVal) -> { if(marked) { getStyleClass().remove(styleClass); marked = false; this.applyCss(); } else if(this.getIndex() == newVal.intValue()) { getStyleClass().add(styleClass); marked = true; this.applyCss(); } }); indexProperty().addListener((obs, oldVal, newVal) -> { if(newVal.intValue() == index.get()) { getStyleClass().add(styleClass); marked = true; } else { getStyleClass().remove(styleClass); marked = false; } }); } } }